package com.xms.sdk.client;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.xms.core.enums.AccessRightValue;
import com.xms.core.enums.OperationStage;
import com.xms.core.enums.OperationType;
import com.xms.plugin.service.IEntityPluginExecutor;
import com.xms.schema.core.CascadeUpdateType;
import com.xms.schema.core.EntityMask;
import com.xms.schema.entity.Attribute;
import com.xms.schema.entity.Entity;
import com.xms.schema.entity.RelationShip;
import com.xms.schema.service.attribute.IAttributeFinderService;
import com.xms.schema.service.relationship.IRelationShipFinderService;
import com.xms.sdk.core.OwnerObject;
import com.xms.sdk.core.OwnerTypes;
import com.xms.sdk.core.RetrieverParameter;
import com.xms.sdk.core.query.ConditionOperator;
import com.xms.sdk.core.query.QueryExpression;
import com.xms.sdk.data.IOrganizationDataProvider;
import com.xms.sdk.data.IOrganizationDataRetriever;
import com.xms.sdk.event.EntityAssignedEvent;
import com.xms.sdk.event.EntityAssigningEvent;
import com.xms.sdk.extensions.IQueryMetadataFinder;
import com.xms.sdk.service.IDataAssigner;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;

/**
 * <p>
 * DataAssigner
 * </p>
 *
 * @author migo
 * @since 2021/9/6
 */
@Service
public class DataAssigner extends DataProviderBase implements IDataAssigner {
    private IOrganizationDataProvider _organizationDataProvider;
    private IOrganizationDataRetriever _organizationDataRetriever;
    private IAttributeFinderService _attributeFinderService;
    private IRelationShipFinderService _relationShipFinderService;
    private IEntityPluginExecutor _entityPluginExecutor;
    private IQueryMetadataFinder _queryMetadataFinder;

    @Autowired
    public void DataAssigner(IOrganizationDataProvider organizationDataProvider,
                             IOrganizationDataRetriever organizationDataRetriever,
                             IAttributeFinderService attributeFinderService,
                             IRelationShipFinderService relationShipFinderService,
                             IEntityPluginExecutor entityPluginExecutor,
                             IQueryMetadataFinder queryMetadataFinder) {
        _organizationDataProvider = organizationDataProvider;
        _organizationDataRetriever = organizationDataRetriever;
        _attributeFinderService = attributeFinderService;
        _relationShipFinderService = relationShipFinderService;
        _entityPluginExecutor = entityPluginExecutor;
        _queryMetadataFinder = queryMetadataFinder;
    }

    @Override
    public boolean assign(UUID entityId, UUID recordId, OwnerObject owner, boolean ignorePermissions) throws Exception {
        com.xms.schema.entity.Entity entityMetadata = _entityFinderService.getById(entityId);
        QueryExpression query = new QueryExpression(entityMetadata.getName());
        query.ColumnSet.AllColumns = true;
        query.Criteria.addCondition(entityMetadata.getName() + "id", ConditionOperator.Equal, recordId);
        com.xms.core.data.Entity entity = _organizationDataRetriever.getOne(
                RetrieverParameter.build(query, _currentUser)
                        .setIgnorePermissions(ignorePermissions)
                .setEntityPermissions(getUserEntityPermissions(query, AccessRightValue.Read))
        ).data;

        return this.assign(entityMetadata, entity, owner, ignorePermissions);
    }

    @Override
    public boolean assign(com.xms.schema.entity.Entity entityMetadata, com.xms.core.data.Entity entity, OwnerObject owner, boolean ignorePermissions) throws Exception {
        if (!ignorePermissions) {
            verifyEntityPermission(entity, AccessRightValue.Assign, entityMetadata);
        }
        UUID recordId = entity.getIdValue();
        com.xms.core.data.Entity ownerEntity = null;
        com.xms.core.data.Entity updateEntity = new com.xms.core.data.Entity(entityMetadata.getName());
        updateEntity.setIdValue(recordId);
        updateEntity.setValue("ownerid", owner);
        UUID owningbusinessunit = null;
        if (owner.OwnerType == OwnerTypes.SystemUser) {
            if (owner.OwnerId.equals(_currentUser.getUser().SystemUserId)) {
                owningbusinessunit = _currentUser.getUser().BusinessUnitId;
                updateEntity.setValue("owningbusinessunit", owningbusinessunit);
            } else {
                //business unit
                QueryExpression queryBusinessUnit = new QueryExpression("systemuser");
                queryBusinessUnit.ColumnSet.addColumn("businessunitid");
                queryBusinessUnit.Criteria.addCondition("systemuserid", ConditionOperator.Equal, owner.OwnerId);
                ownerEntity = _organizationDataRetriever.getOne(RetrieverParameter.build(queryBusinessUnit, _currentUser).setIgnorePermissions(true)).data;
                owningbusinessunit = ownerEntity.getUUIDValue("businessunitid");
                updateEntity.setValue("owningbusinessunit", owningbusinessunit);
            }
            updateEntity.setValue("modifiedon", java.time.LocalDateTime.now());
            updateEntity.setValue("modifiedby", _currentUser.getUser().SystemUserId);
        } else {
            updateEntity.setValue("owningbusinessunit", null);
        }
        List<Attribute> attributeMetadatas = _attributeFinderService.getByEntityId(entityMetadata.getEntityid());
        boolean result = true;
        try {
            internalOnAssign(entity, updateEntity, OperationStage.PreOperation, entityMetadata, attributeMetadatas);
            result = _organizationDataProvider.update(updateEntity);
            if (result) {
                //assign cascade relationship, 1: N
                List<RelationShip> relationships = _relationShipFinderService.list(new QueryWrapper<RelationShip>()
                        .lambda()
                        .eq(RelationShip::getReferencedentityid, entityMetadata.getEntityid())
                        .ne(RelationShip::getCascadeassign, CascadeUpdateType.None));
                if (relationships.isEmpty() == false) {
                    for (RelationShip rs : relationships) {
                        com.xms.schema.entity.Entity relatedEntityMeta = _entityFinderService.getById(rs.getReferencingentityid());
                        if (relatedEntityMeta.getEntitymask() == EntityMask.Organization.value()) {
                            continue;
                        }

                        QueryExpression queryRelated = new QueryExpression(rs.getReferencingentityname());
                        queryRelated.ColumnSet.addColumns(rs.getReferencingentityname() + "id");
                        queryRelated.Criteria.addCondition(rs.getReferencingattributename(), ConditionOperator.Equal, recordId);
                        //update related records
                        com.xms.core.data.Entity updEntity = new com.xms.core.data.Entity(rs.getReferencingentityname());
                        updEntity.setValue("ownerid", owner);
                        if (owner.OwnerType == OwnerTypes.SystemUser) {
                            updEntity.setValue("owningbusinessunit", owningbusinessunit);
                        } else {
                            updEntity.setValue("owningbusinessunit", null);
                        }
                        updEntity.setValue("modifiedon", LocalDateTime.now());
                        updEntity.setValue("modifiedby", _currentUser.getUser().SystemUserId);
                        RetrieverParameter parameters = RetrieverParameter.build(queryRelated, _currentUser).setIgnorePermissions(true).setIncludeNameField(false);
                        _organizationDataProvider.update(updEntity, parameters, _queryMetadataFinder.getAll(queryRelated));
                    }
                }
                internalOnAssign(entity, updateEntity, OperationStage.PostOperation, entityMetadata, attributeMetadatas);
            }
        } catch (Exception e) {
            return onException(e);
        }
        return true;
    }

    @Override
    public boolean assign(UUID entityId, UUID recordId, OwnerObject owner) throws Exception {
        return this.assign(entityId, recordId, owner, false);
    }

    @Override
    public boolean assign(Entity entityMetadata, com.xms.core.data.Entity entity, OwnerObject owner) throws Exception {
        return this.assign(entityMetadata, entity, owner, false);
    }

    private void internalOnAssign(com.xms.core.data.Entity data, com.xms.core.data.Entity newOwnerEntity, OperationStage stage, com.xms.schema.entity.Entity entityMetadata, List<Attribute> attributeMetadatas) throws Exception {
        //plugin
        _entityPluginExecutor.execute(OperationType.Assign, stage, newOwnerEntity, entityMetadata, attributeMetadatas);

        publishEvents(data, newOwnerEntity, stage, entityMetadata);
    }

    // 发布事件
    private void publishEvents(com.xms.core.data.Entity data, com.xms.core.data.Entity newOwnerEntity, OperationStage stage, com.xms.schema.entity.Entity entityMetadata) {
        if (stage == OperationStage.PreOperation) {
            _eventPublisher.publish(new EntityAssigningEvent(data, newOwnerEntity, entityMetadata));
        } else if (stage == OperationStage.PostOperation) {
            _eventPublisher.publish(new EntityAssignedEvent(data, newOwnerEntity, entityMetadata));
        }
    }
}
